Azure DDoS Protection/Query - DDOS Protected IPs/Get-AllDdosProtectedIPs.ps1 (459 lines of code) (raw):

<# .SYNOPSIS This script will loop through all vnets for a list of subscriptions and determine which public IP addresses are protected or unprotected .Description Given a list of subscriptions this script will iterate all subscriptions and resources to find whether or not resources are protected by DDOS .PARAMETER SubscriptionIds String[]: Mandatory. A list of subscription ids to search .PARAMETER CSVRootFileName String: Mandatory if OutToCsv is flagged. A root file name for the output csv(s) of this function. Only enabled if OutToCsv is flagged. Files will be overwritten if they already exist For each subscription given to this command two CSV will output with name format: (current_directory)/$CSVRootFileName_SubscriptionID_ProtectionEnabled and (current_directory)/$CSVRootFileName_SubscriptionID_ProtectionDisabled Contents of CSV will be: Name ResourceGroup Type Location PublicIpAddress (if it has one) DdosProtected .PARAMETER OutToCsv String: Optional - If flagged, uses the CSVRootFileName to output the results of this search .PARAMETER SpecifyResourceGroups String[]: Optional - If present, only resource groups with this names will be searched .OUTPUTS Outputs a dictionary of the format { "SubscriptionId": { "Enabled": { "VnetId1": [ //Note these are the actual resource obtained by Get-Az<Resource> "Resource1", "Resource2" ] }, "Disabled": { "VnedId2"[ "Resource3", "Resource4" ] } ] } } .NOTES This script may run for a long time depending on how many resources you have in each subscription .EXAMPLE $subDdosResources = (./Get-AllDdosProtectedIPs -SubscriptionIds 50d98c97-28f0-4034-88f6-fe47d3334d7c) Finds all DDOS enabled and disabled resources under the above subscription. Returns an object { "50d98c97-28f0-4034-88f6-fe47d3334d7c": { "Enabled": { "Vnets": [Resources ]}, "Disabled": { "Vnets": [Resources ]}} .EXAMPLE $subids = @("2a6f6cf4-092b-42f8-8f80-105dc153db2d", "164d0efe-e05c-4096-afbf-7b2648cb5b61") $subDdosResource = (./Get-AllDdosProtectedIPs -OutToCsv -SubscriptionIds $subids) Finds all DDOS enabled and disabled resources under the two subscriptions. Four CSV files will be generated using default $CSVRootFileName: (current_directory)/Azure_PublicIp_AndVnet_DdosAnalysis_2a6f6cf4-092b-42f8-8f80-105dc153db2d_ProtectionEnabled.csv (current_directory)/Azure_PublicIp_AndVnet_DdosAnalysis_2a6f6cf4-092b-42f8-8f80-105dc153db2d_ProtectionDisabled.csv (current_directory)/Azure_PublicIp_AndVnet_DdosAnalysis_164d0efe-e05c-4096-afbf-7b2648cb5b61_ProtectionEnabled.csv (current_directory)/Azure_PublicIp_AndVnet_DdosAnalysis_164d0efe-e05c-4096-afbf-7b2648cb5b61_ProtectionDisabled.csv Returns an object { "2a6f6cf4-092b-42f8-8f80-105dc153db2d": { "Enabled": { "Vnets": [Resources ]}, "Disabled": { "Vnets": [Resources]}, "164d0efe-e05c-4096-afbf-7b2648cb5b61": { "Enabled": { "Vnets": [Resources ]}, "Disabled": { "Vnets": [Resources]} } .EXAMPLE $subids = @("2a6f6cf4-092b-42f8-8f80-105dc153db2d", "164d0efe-e05c-4096-afbf-7b2648cb5b61") $subDdosResource = (./Get-AllDdosProtectedIPs -OutToCsv -CSVRootFileName "My_filename_root" -SubscriptionIds $subids) Outputs same as above example, except file names will be: (current_directory)/My_filename_root_<subid>_<ProtectionEnabled/ProtectionDisabled>csv .EXAMPLE $subids = @("2a6f6cf4-092b-42f8-8f80-105dc153db2d") $subDdosResource = (./Get-AllDdosProtectedIPs -SpecifyResourceGroups myResourceGroup, otherResourceGroup -SubscriptionIds $subids) No CSV will be output, but all resources under subscription "2a6f6cf4-092b-42f8-8f80-105dc153db2d" that are in resourceGroup 'myResourceGroup' or 'otherResourceGroup' will be checked Return object has same format { "2a6f6cf4-092b-42f8-8f80-105dc153db2d": { "Enabled": { "Vnets": [Resources]}, "Disabled": { "Vnets": [Resources]}} #> param( [Parameter(Mandatory=$True)] [string[]] $SubscriptionIds = @(""), [string] $CSVRootFileName = "Azure_PublicIp_AndVnet_DdosAnalysis", [string[]] $SpecifyResourceGroups = $null, [switch] $OutToCsv ) #Many resources are cyclically referenced - this hashset ensures we do not add a resource more than once $DiscoveredResources = New-Object System.Collections.Generic.HashSet[string] <# Helper, ensure we can write to CSV before iterating whole sub #> function Test-FileCanBeWritten { param ( [parameter(Mandatory=$true)][string]$Path ) $oFile = New-Object System.IO.FileInfo $Path #lack permissions Try { [io.file]::OpenWrite($Path).close() } Catch { return $false } #next we check if file is open # file does not exist, can't be open, we can write to it if ((Test-Path -Path $Path) -eq $false) { return $true } # check if we can write to the file, if not it is open try { $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) if ($oStream) { $oStream.Close() } #will fail if it is locked return $true } catch { # file is locked return $false } } function Add-ResourceToVnetDict{ param( [object] $EnabledDict, [object] $DisabledDict, [object] $Resource, [object] $Vnet ) #this should not happen, but protects against the case that vnet failed to get resolved in one of the components if($null -eq $Vnet -or $null -eq $Vnet.Id){ $Vnet = "UnknownVnet" } $curDict = $EnabledDict #assume an unknown Vnet does not have ddos protection to be safe if($Vnet -eq "UnknownVnet" -or !$Vnet.EnableDdosProtection){ $curDict = $DisabledDict } if($null -ne $Resource){ if($DiscoveredResources.Contains($Resource.Id)){ return } $DiscoveredResources.Add($Resource.Id) | Out-Null if(!$curDict.ContainsKey("$($Vnet.Id)")){ #$vpn.IpConfigurations | ConvertTo-Json $curDict["$($Vnet.Id)"] = @($Resource) }else{ $curDict["$($Vnet.Id)"] += @($Resource) } } } function Resolve-VirtualMachineScaleSetIpConfToVnet{ param( [object] $EnabledDict, [object] $DisabledDict, [object] $Vnets, [object] $IpConfId ) $vmssName = [regex]::match($IpConfId, '/virtualMachineScaleSets/([^/]*)/').Groups[1].Value $rg = [regex]::match($IpConfId, '/resourceGroups/([^/]*)/').Groups[1].Value $myVnet = "UnknownVnet" #try to match our ip config with one in one of the vnets foreach($vnet in $Vnets){ foreach($subnet in $vnet.Subnets){ foreach($ipconf in $subnet.IpConfigurations){ if($ipconf.Id -eq $IpConfId){ $myVnet = $vnet } } } } $vmss = Get-AzVmss -ResourceGroupName $rg -VMScaleSetName $vmssName Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $vmss -Vnet $myVnet } function Resolve-NicIpsToVnet{ param( [object] $EnabledDict, [object] $DisabledDict, [object] $Vnet, [object] $Nics ) #get all public ips that have a public ip address foreach($nic in $Nics){ $ipId = ($nic.PublicIPAddress.Id) if($ipId){ $ipName = [regex]::match($ipId, '/publicIPAddresses/([^/]*)').Groups[1].Value $ipRg = [regex]::match($ipId, '/resourceGroups/([^/]*)').Groups[1].Value $ipR = Get-AzPublicIpAddress -Name $ipName -ResourceGroupName $ipRg if($ipR){ $ips += @($ipR) } } } foreach($ip in $ips) { Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $ip -Vnet $Vnet } } function Resolve-LoadBalancerIps{ param( [object] $Vnets, [object] $EnabledDict, [object] $DisabledDict, [string] $SpecifyResourceGroups ) Write-Host "Resolving Load Balancer IPs..." -ForegroundColor Yellow $slbs = (Get-AzLoadBalancer) if($SpecifyResourceGroups){ $slbs = ($slbs | Where-Object{ $_.ResourceGroupName -in $SpecifyResourceGroups }) } foreach($slb in $slbs){ foreach($pool in $slb.BackendAddressPools){ foreach($conf in $pool.BackendIpConfigurations){ if($conf.Id.Contains("virtualMachineScaleSets")){ $id = $conf.Id $vmssName = [regex]::match($id, '/virtualMachineScaleSets/([^/]*)/').Groups[1].Value $myVnet = "UnknownVnet" #try to match our ip config with one in one of the vnets foreach($vnet in $Vnets){ foreach($subnet in $vnet.Subnets){ foreach($ipconf in $subnet.IpConfigurations){ if($ipconf.Id -eq $id){ $myVnet = $vnet } } } } $vmss = Get-AzVmss -ResourceGroupName $rg -VMScaleSetName $vmssName Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $vmss -Vnet $myVnet $ipid = $slb.FrontendIpConfigurations.PublicIpAddress.Id if($ipid){ $resourceGroup = [regex]::match($ipid, '/resourceGroups/([^/]*)/').Groups[1].Value $ipName = [regex]::match($ipid, '/publicIPAddresses/([^/]*)').Groups[1].Value $ipR = Get-AzPublicIpAddress -Name $ipName -ResourceGroupName $resourceGroup Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $ipR -Vnet $myVnet } #concept is whole vmscaleset shares the same vnet, therefore break out of this loop break; }else{ # A generic VM behind a load balancer will get resolved when we iterate virtual networks # this is found later in the script. if you want to run this function as a standalone, uncomment below code and remove break break; # Write-Host "Resolving VM" # $id = $conf.Id # $nic = [regex]::match($id, '/networkInterfaces/([^/]*)/').Groups[1].Value # $rg = [regex]::match($id, '/resourceGroups/([^/]*)/').Groups[1].Value # $nicResource = Get-AzNetworkInterface -ResourceGroupName $rg -Name $nic # $subnetId = $nicResource.IpConfigurations[0].Subnet.Id # $ipId = $nicResource.IpConfigurations[0].PublicIpAddress.Id # if(!$ipId){ # #no public ip assigned to this resource, continue # continue; # } # $vnetName = [regex]::match($subnetId, '/virtualNetworks/([^/]*)/').Groups[1].Value # $vnetRg = [regex]::match($subnetId, '/resourceGroups/([^/]*)/').Groups[1].Value # #note missing / after ip string # $ipName = [regex]::match($ipId, '/publicIPAddresses/([^/]*)').Groups[1].Value # $ipRg = [regex]::match($ipId, '/resourceGroups/([^/]*)/').Groups[1].Value # $vnet = $Vnets | Where-Object {$_.Name -eq $vnetName -and $_.ResourceGroupName -eq $vnetRg } # $ip = Get-AzPublicIpAddress -Name $ipName -ResourceGroupName $ipRg # Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $ip -Vnet $vnet } } } } } function Resolve-AppGatewayVnetToIps{ param( [object] $Vnets, [object] $EnabledDict, [object] $DisabledDict, [object] $SpecifyResourceGroups ) Write-Host "Resolving Application Gateway IPs..." -ForegroundColor Yellow $appGateways = Get-AzApplicationGateway if($SpecifyResourceGroups){ $appGateways = ($appGateways | Where-Object{ $_.ResourceGroupName -in $SpecifyResourceGroups }) } foreach($gateway in $appGateways){ $id = $gateway.HttpListeners.FrontendIpConfiguration.Id $resourceGroup = [regex]::match($id, '/resourceGroups/([^/]*)/').Groups[1].Value $ipConfName = [regex]::match($id, '/frontendIPConfigurations/([^/]*)').Groups[1].Value $ipConf = Get-AzApplicationGatewayFrontendIPConfig -Name $ipConfName -ApplicationGateway $gateway if($ipConf.PublicIPAddress){ $subnetId = $gateway.GatewayIPConfigurations.Subnet.Id $vnetRg = [regex]::match($subnetId, '/resourceGroups/([^/]*)/').Groups[1].Value $vnetName = [regex]::match($subnetId, '/virtualNetworks/([^/]*)/').Groups[1].Value $myVnet = "UnknownVnet" foreach($vnet in $Vnets){ if($vnet.ResourceGroupName -eq $vnetRg -and $vnet.Name -eq $vnetName){ $myVnet = $vnet } } $ipId = $ipConf.PublicIPAddress.Id $resourceGroup = [regex]::match($ipId, '/resourceGroups/([^/]*)/').Groups[1].Value $ipName = [regex]::match($ipId, '/publicIPAddresses/([^/]*)').Groups[1].Value $ip = Get-AzPublicIpAddress -ResourceGroupName $resourceGroup -Name $ipName Add-ResourceToVnetDict -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Resource $ip -Vnet $myVnet } } } function Format-DdosProtectionTable{ param( [object] $DdosDictionary, [object] $Vnets, [bool] $Enabled ) $color = if($Enabled) { "Green" } else { "Red" } $enabledString = if($Enabled) { "protected by DDOS"} else { "NOT protected by DDOS" } $formatHeader = "{0}{1}{2}{3}{4}{5}" -f "Name".PadRight(25), "ResourceGroup".PadRight(25), "Type".PadRight(60), "Location".PadRight(25), "PublicIpAddress".PadRight(25), "DdosProtected".PadRight(20) $dash = "{0}" -f "".PadRight(180,'-') $formatStr = "{0}{1}{2}{3}{4}{5}" $OutObjects = @() foreach($vnet in $DdosDictionary.Keys){ foreach($resource in $DdosDictionary[$vnet]){ $OutObjects += @( [PSCustomObject]@{ Name = $resource.Name; ResourceGroupName = $resource.ResourceGroupName; Type = $resource.Type; Location = $resource.Location; DdosProtected = $Enabled; #will be null if resource is not an ip address PublicIpAddress = $resource.IpAddress; Id = $resource.Id; VirtualNetworkId = $vnet; }) } } Write-Host "The following Vnets are $enabledString" -ForegroundColor Yellow $Vnets | Write-Host -ForegroundColor $color Write-Host " " Write-Host "These are the resources under those VNETs:" -ForegroundColor Yellow Write-Host $formatHeader -ForegroundColor $color Write-Host $dash -ForegroundColor $color $OutObjects | Foreach-Object { $formatted = $formatStr -f "$($_.Name)".PadRight(25), "$($_.ResourceGroupName)".PadRight(25), ` "$($_.Type)".PadRight(60), "$($_.Location)".PadRight(25), "$($_.PublicIpAddress)".PadRight(25),"$($_.DdosProtected)".PadRight(20); Write-Host $formatted -ForegroundColor $color; } Write-Host " " return $OutObjects } if($OutToCsv -and !$CSVRootFileName){ Write-Host "CSVRootFileName must be specified if OutToCsv is flagged" -ForegroundColor Red return } # Ensure that we can write to where the csvs will be written if($OutToCsv){ $cwd = Get-Location foreach($subId in $SubscriptionIds){ $csvFile = "$($cwd)`\$($CSVRootFileName)_$($subscriptionId)_ProtectionEnabled.csv" if (!(Test-FileCanBeWritten $csvFile)){ Write-Host "Unable to write to " -ForegroundColor Red -NoNewline; Write-Host $csvFile -ForegroundColor Cyan -NoNewline; Write-Host " which would be generated by this script." -ForegroundColor Red Write-Host "-> Please check if file is open or if you have access to this directory." -ForegroundColor Red return } $csvFile = "$($cwd)`\$($CSVRootFileName)_$($subscriptionId)_ProtectionDisabled.csv" if (!(Test-FileCanBeWritten $csvFile)){ Write-Host "Unable to write to " -NoNewline; Write-Host $csvFile -ForegroundColor Cyan -NoNewline; Write-Host " which would be generated by this script." -ForegroundColor Red Write-Host "-> Please check if file is open or if you have access to this directory." -ForegroundColor Red return } } } $subIdToDicts = @{} foreach($subscriptionId in $SubscriptionIds){ $EnabledDict = @{} $DisabledDict = @{} $vnetsNotEnabled = @() $vnetsEnabled = @() Write-Host "Finding resources under subscription $subscriptionId" $context = Get-AzContext #if we need to, log in if($null -eq $context.Account) { Connect-AzAccount -Subscription $subscriptionId | Out-Null } #in case we weren't logged in, context will have changed $context = Get-AzContext #check if connected to right subscription id if(-not ($context.name -like ("*" + $subscriptionId + "*"))){ Write-Host "Acquiring subscription for this resource" Get-AzSubscription -subscriptionid $subscriptionId | Select-AzSubscription } Write-Host "Acquiring virtual networks..." -ForegroundColor Yellow $vnets = Get-AzVirtualNetwork if($SpecifyResourceGroups){ $vnets = ($vnets | Where-Object{ $_.ResourceGroupName -in $SpecifyResourceGroups }) } Resolve-AppGatewayVnetToIps -Vnets $vnets -EnabledDict $EnabledDict -DisabledDict $DisabledDict -SpecifyResourceGroups $SpecifyResourceGroups Resolve-LoadBalancerIps -Vnets $vnets -EnabledDict $EnabledDict -DisabledDict $DisabledDict -SpecifyResourceGroups $SpecifyResourceGroups $ipconfCount = $vnets.Subnets.IpConfigurations.Count $curConf = 0 #resolve the rest foreach($vnet in $vnets){ if($vnet.EnableDdosProtection){ $curDict = $vnetToIpMapping $vnetsEnabled += @($vnet.Id) }else{ $curDict = $vnetToIpMappingNoDdos $vnetsNotEnabled += @($vnet.Id) } Write-Host "Getting ips behind vnet $($vnet.Name)" -ForegroundColor Yellow $subnets = $vnet.Subnets Write-Host "$($subnets.Count) subnets behind this vnet" foreach($subnet in $subnets){ $ipconfigs = $subnet.IpConfigurations if($ipconfigs.Count -eq 0){ #App gateway will have 0 ip configs continue; } Write-Host "$($ipconfigs.Count) ip configurations behind this subnet: $($subnet.Name)" foreach($ipconf in $ipconfigs) { Write-Progress -Activity "Iterating VNET: $($vnet.Name)" -Status "Progress for Sub: $subscriptionId ->" ` -PercentComplete (($curConf * 100) / $ipconfCount) $resourceType = [regex]::match($ipconf.Id, '/Microsoft.Network/([^/]*)/').Groups[1].Value if($ipconf.Id.Contains("virtualMachineScaleSets")){ Resolve-VirtualMachineScaleSetIpConfToVnet -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Vnets $vnets -IpConfId $ipconf.id }elseif($resourceType -eq "networkInterfaces"){ $resourceGroup = [regex]::match($ipconf.Id, '/resourceGroups/([^/]*)/').Groups[1].Value $networkInterface = [regex]::match($ipconf.Id, '/networkInterfaces/([^/]*)/').Groups[1].Value $nics = (Get-AzNetworkInterface -Name $networkInterface -ResourceGroupName $resourceGroup).IpConfigurations Resolve-NicIpsToVnet -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Vnet $vnet -Nics $nics }elseif($resourceType -eq "azureFirewalls"){ $firewallName = [regex]::match($ipconf.Id, '/azureFirewalls/([^/]*)/').Groups[1].Value $resourceGroup = [regex]::match($ipconf.Id, '/resourceGroups/([^/]*)/').Groups[1].Value $nics = (Get-AzFirewall -ResourceGroupName $resourceGroup -Name $firewallName).IpConfigurations Resolve-NicIpsToVnet -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Vnet $vnet -Nics $nics }elseif($resourceType -eq "virtualNetworkGateways"){ $resourceName = [regex]::match($ipconf.Id, '/virtualNetworkGateways/([^/]*)/').Groups[1].Value $resourceGroup = [regex]::match($ipconf.Id, '/resourceGroups/([^/]*)/').Groups[1].Value $nics = (Get-AzVirtualNetworkGateway -Name $resourceName -ResourceGroupName $resourceGroup).IpConfigurations Resolve-NicIpsToVnet -EnabledDict $EnabledDict -DisabledDict $DisabledDict -Vnet $vnet -Nics $nics } $curConf += 1 } } } $OutObjectsDisabled = Format-DdosProtectionTable -DdosDictionary $DisabledDict -Vnets $vnetsNotEnabled -Enabled $False $OutObjectsEnabled = Format-DdosProtectionTable -DdosDictionary $EnabledDict -Vnets $vnetsEnabled -Enabled $True if($OutToCsv){ $cwd = Get-Location $csvFile = "$($cwd)`\$($CSVRootFileName)_$($subscriptionId)_ProtectionDisabled.csv" $OutObjectsDisabled | Export-Csv -Path $csvFile -NoTypeInformation $csvs += @($csvFile) $csvFile = "$($cwd)`\$($CSVRootFileName)_$($subscriptionId)_ProtectionEnabled.csv" $OutObjectsEnabled | Export-Csv -Path $csvFile -NoTypeInformation $csvs += @($csvFile) } $subIdToDicts[$subscriptionId] = @{} $subIdToDicts[$subscriptionId]["Enabled"] = $EnabledDict; $subIdToDicts[$subscriptionId]["Disabled"] = $DisabledDict; } if($OutToCsv){ Write-Host " " Write-Host "The following files have been generated" -ForegroundColor Yellow $csvs | Write-Host -ForegroundColor Green } return $subIdToDicts